/*
 * Copyright 2011 Kim Lindhardt Madsen
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package dk.lindhardt.gwt.geie.client.evaluate;

import org.matheclipse.parser.client.ast.FunctionNode;
import org.matheclipse.parser.client.ast.SymbolNode;

import java.util.HashMap;
import java.util.Map;

/**
 * User: AnAmuser
 * Date: 24-08-11
 * <p/>
 * Evaluate expressions to <code>boolean</code> values
 */
public class GeieBooleanEvaluator {

   static Map<String, Boolean> SYMBOL_BOOLEAN_MAP;
   static Map<String, Object> FUNCTION_BOOLEAN_MAP;

   public static double EPSILON = 1.0e-15;

   static void addFunction(String name, IFunction function) {
      function.setFunctionName(name);
      FUNCTION_BOOLEAN_MAP.put(name, function);
   }

   static {
      SYMBOL_BOOLEAN_MAP = new HashMap<String, Boolean>();
      SYMBOL_BOOLEAN_MAP.put("false", Boolean.FALSE);
      SYMBOL_BOOLEAN_MAP.put("true", Boolean.TRUE);

      FUNCTION_BOOLEAN_MAP = new HashMap<String, Object>();
      FUNCTION_BOOLEAN_MAP.put(
            "false", new IBoolean0Function() {
               public boolean evaluate() {
                  return false;
               }
            });
      FUNCTION_BOOLEAN_MAP.put(
            "true", new IBoolean0Function() {
               public boolean evaluate() {
                  return true;
               }
            });
      addFunction(
            "and", new IBooleanBoolean2Function() {
               @Override
               Boolean evaluateBoolean(boolean arg1, boolean arg2) {
                  return arg1 && arg2;
               }
            });
      addFunction(
            "not", new IBooleanBoolean1Function() {
               @Override
               Boolean evaluateBoolean(boolean arg1) {
                  return !arg1;
               }
            });
      addFunction(
            "or", new IBooleanBoolean2Function() {
               @Override
               Boolean evaluateBoolean(boolean arg1, boolean arg2) {
                  return arg1 || arg2;
               }
            });
      addFunction(
            "equal", new IBooleanDouble2Function() {
               @Override
               Boolean evaluateBoolean(double arg1, double arg2) {
                  return Math.abs(arg1 - arg2) < EPSILON;
               }
            });
      addFunction(
            "greater", new IBooleanDouble2Function() {
               public Boolean evaluateBoolean(double arg1, double arg2) {
                  return arg1 > arg2;
               }
            });
      addFunction(
            "greaterequal", new IBooleanDouble2Function() {
               public Boolean evaluateBoolean(double arg1, double arg2) {
                  return arg1 >= arg2;
               }
            });
      addFunction(
            "less", new IBooleanDouble2Function() {
               public Boolean evaluateBoolean(double arg1, double arg2) {
                  return arg1 < arg2;
               }
            });
      addFunction(
            "lessequal", new IBooleanDouble2Function() {
               public Boolean evaluateBoolean(double arg1, double arg2) {
                  return arg1 <= arg2;
               }
            });
      addFunction(
            "unequal", new IBooleanDouble2Function() {
               public Boolean evaluateBoolean(double arg1, double arg2) {
                  return !(Math.abs(arg1 - arg2) < EPSILON);
               }
            });
   }

   private GeieEvaluator geieEvaluator;

   /**
    * New boolean evaluator
    * @param geieEvaluator
    */
   public GeieBooleanEvaluator(GeieEvaluator geieEvaluator) {
      this.geieEvaluator = geieEvaluator;
   }

   Boolean evaluateFunction(final FunctionNode functionNode) {
      if (functionNode.size() > 0
            && functionNode.getNode(0) instanceof SymbolNode) {
         String symbol = functionNode.getNode(0).toString().toLowerCase();
         if (functionNode.size() == 1) {
            Object obj = FUNCTION_BOOLEAN_MAP.get(symbol);
            if (obj instanceof IBoolean0Function) {
               return ((IBoolean0Function) obj).evaluate();
            }
         } else if (functionNode.size() == 2) {
            Object obj = FUNCTION_BOOLEAN_MAP.get(symbol);
            if (obj instanceof IBooleanBoolean1Function) {
               return ((IBooleanBoolean1Function) obj)
                     .evaluate(geieEvaluator.evaluateNode(functionNode.getNode(1)));
            }
         } else if (functionNode.size() == 3) {
            Object obj = FUNCTION_BOOLEAN_MAP.get(symbol);
            if (obj instanceof IBooleanDouble2Function) {
               return ((IBooleanDouble2Function) obj).evaluate(
                     geieEvaluator.evaluateNode(functionNode.getNode(1)),
                     geieEvaluator.evaluateNode(functionNode.getNode(2)));
            } else if (obj instanceof IBooleanBoolean2Function) {
               return ((IBooleanBoolean2Function) obj).evaluate(
                     geieEvaluator.evaluateNode(functionNode.getNode(1)),
                     geieEvaluator.evaluateNode(functionNode.getNode(2)));
            }
         }
      }
      throw new ArithmeticException("Evaluating not possible for: " + functionNode.toString());

   }
}
